package com.qg.log.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSONObject;
import com.qg.auth.util.WebContextUtil;
import com.qg.base.constants.LogConstant;
import com.qg.log.entity.OptLogDTO;
import com.qg.base.entity.R;
import com.qg.log.event.SysLogEvent;
import com.qg.log.util.LogUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * @Author QG
 * @Date 2023/5/29 15:52
 * @description 日志切面
 **/
@Slf4j
@Aspect
public class SysLogAspect {

    /**
     * 事件发布是由ApplicationContext对象管控的，我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布
     **/
    @Autowired
    private ApplicationContext applicationContext;

    private static final ThreadLocal<OptLogDTO> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 定义controller切入点拦截规则，拦截SysLog注解的方法(切点)
     */
    @Pointcut("@annotation(com.qg.log.annotation.SysLog)")
    public void sysLogAspect() {
    }

    /**
     * 方法前置增强
     */
    @Before(value = "sysLogAspect()")
    public void before(JoinPoint joinPoint) {
        tryCatch(value -> {
            OptLogDTO optLogDTO = get();
            //日志记录创建人和名称设置
            Map userInfo = WebContextUtil.getUserInfo();
            optLogDTO.setCreateUser((Long) userInfo.get(LogConstant.LOG_USER_ID));
            optLogDTO.setUserName((String) userInfo.get(LogConstant.LOG_USER_NAME));

            //记录描述信息
            String controllerDescription = "";
            Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
            if (null != api) {
                String[] tags = api.tags();
                if (tags != null && tags.length > 0) {
                    controllerDescription += tags[0];
                }
            }
            String controllerMethodDescription = LogUtil.getControllerMethodDescription(joinPoint);
            if (StrUtil.isEmpty(controllerDescription)) {
                optLogDTO.setDescription(controllerMethodDescription);
            } else {
                optLogDTO.setDescription(controllerDescription + "-" + controllerMethodDescription);
            }
            //设置类名
            optLogDTO.setClassPath(joinPoint.getTarget().getClass().getName());
            //设置执行方法名
            optLogDTO.setActionMethod(joinPoint.getSignature().getName());
            //设置参数
            Object[] args = joinPoint.getArgs();
            String strArgs = "";
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            try {
                if (!request.getContentType().contains("multipart/form-data")) {
                    strArgs = JSONObject.toJSONString(args);
                }
            } catch (Exception e) {
                try {
                    strArgs = Arrays.toString(args);
                } catch (Exception ex) {
                    log.warn("解析参数异常", ex);
                }
            }
            optLogDTO.setParams(getText(strArgs));
            //设置其他信息
            if (request != null) {
                optLogDTO.setRequestIp(ServletUtil.getClientIP(request));
                optLogDTO.setRequestUri(URLUtil.getPath(request.getRequestURI()));
                optLogDTO.setHttpMethod(request.getMethod());
                optLogDTO.setUa(StrUtil.sub(request.getHeader("user-agent"), 0, 500));
            }
            //设置开始时间
            optLogDTO.setStartTime(LocalDateTime.now());
            //将日志对象保存到ThreadLocal中，用于后续操作
            THREAD_LOCAL.set(optLogDTO);
        });
    }

    /**
     * 方法后置增强
     *
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
    public void after(Object ret) {
        tryCatch(a -> {
            //获取日志对象
            OptLogDTO optLogDTO = THREAD_LOCAL.get();
            R r = Convert.convert(R.class, ret);
            optLogDTO.setResult(JSONObject.toJSONString(r.getData()));
            if (r == null) {
                optLogDTO.setType("OPT");
            } else {
                if(r.getIsSuccess()) {
                    optLogDTO.setType("OPT");
                }else{
                    optLogDTO.setType("EX");
                    optLogDTO.setExDetail(r.getMsg());
                }
            }
            optLogDTO.setFinishTime(LocalDateTime.now());
            optLogDTO.setConsumingTime(optLogDTO.getStartTime().until(optLogDTO.getFinishTime(), ChronoUnit.MILLIS));
            //发布事件
            publishEvent(optLogDTO);
        });

    }

    /**
     * 发布日志事件
     * @param optLogDTO
     */
    private void publishEvent(OptLogDTO optLogDTO) {
        optLogDTO.setCreateTime(LocalDateTime.now());
        applicationContext.publishEvent(new SysLogEvent(optLogDTO));
        THREAD_LOCAL.remove();
    }

    private String getText(String val) {
        return StrUtil.sub(val, 0, 65535);
    }


    private OptLogDTO get() {
        OptLogDTO optLogDTO = THREAD_LOCAL.get();
        if (null == optLogDTO) {
            optLogDTO = new OptLogDTO();
        }
        return optLogDTO;
    }


    private void tryCatch(Consumer<String> consumer) {
        try {
            consumer.accept("");
        } catch (Exception e) {
            log.warn("记录操作日志异常", e);
            THREAD_LOCAL.remove();
        }
    }


}
